﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Resources;
using System.Xml.Linq;
using Microsoft.Cci;
using SpCop.Core;
using SpCop.Core.Model;
using SpCop.Model;

namespace SpCop
{
    internal class PackageReader
    {
        private const string Ns = "http://schemas.microsoft.com/sharepoint/";
        private const string LocationAttributeName = "Location";
        
        private static readonly Hashtable ResolverCache = new Hashtable();
        private static readonly PeReader.DefaultHost host = new PeReader.DefaultHost();
        private static readonly XName FeatureName;
        private static readonly XName FeaturesName;
        private static readonly XName SolutionIdXName;
        private static readonly XName ElementManifestName;
        private static readonly XName AssemblyXName;
        private static readonly XName RootFileXName;
        private static readonly XName SiteDefinitionXName;
        private static readonly XName WebTempXName;
        private static readonly XName DependencyXName;


        static PackageReader()
        {
            XName.Get("Solution", Ns);
            FeaturesName = XName.Get("FeatureManifests", Ns);
            FeatureName = XName.Get("FeatureManifest", Ns);
            RootFileXName = XName.Get("RootFile", Ns);
            XName.Get("ElementManifests", Ns);
            ElementManifestName = XName.Get("ElementManifest", Ns);
            AssemblyXName = XName.Get("Assembly", Ns);
            WebTempXName = XName.Get("WebTempFile", Ns);
            SiteDefinitionXName = XName.Get("SiteDefinitionManifest", Ns);
            DependencyXName = XName.Get("ActivationDependency", Ns);
            SolutionIdXName = XName.Get("SolutionId");
            XName.Get("Id");


        }

        internal static Package ReadPackageFromFolder(string packageName, string folderName)
        {
            var package = new Package(packageName, folderName);
            //_resolver.AddSearchDirectory(folderName);
            //_resolver.AddSearchDirectory("lib");
            //_resolver.AddSearchDirectory(folderName);

            string manifestFileName = Path.Combine(folderName, "manifest.xml");
            XDocument xDoc = XDocument.Load(manifestFileName);

           
            var productVersion = xDoc.Root.Attribute("SharePointProductVersion");

            if(productVersion != null)
            {
                if (productVersion.Value == "14.0")
                    package.ProductVersion = ProductVersion.SharePoint2010;
            }
            else
            {
                package.ProductVersion = ProductVersion.SharePoint2007;
            }
            
            if (xDoc.Root != null)
            {
                XAttribute solutionIdAttribute = xDoc.Root.Attribute(SolutionIdXName);
                if (solutionIdAttribute != null)
                    package.Id = new Guid(solutionIdAttribute.Value);


                
                    SetFeatureManifests(xDoc, package);
                    SetFeatures(folderName, package);
               


                SetRootFiles(xDoc, package);


                var siteDefElements = xDoc.Root.Descendants(SiteDefinitionXName);
                package.SiteDefinitions = siteDefElements.Select(ReadSiteDefinition).ToArray();
                //package.Features = package.FeatureManifests.Select(a => ReadFeature(package, folderName, a)).ToArray();


                var xAsselmblies = xDoc.Root.Descendants(AssemblyXName);


                package.Modules =
                    (xAsselmblies.Select(a => Path.Combine(folderName, a.Attribute(LocationAttributeName).Value)).
                        Select(path => host.LoadUnitFrom(path) as IModule)).ToArray();
                
                package.ModuleTypes = package.Modules.SelectMany(a => a.GetAllTypes()).ToArray();


                //package.Assemblies = (
                //                         from a in xAsselmblies
                //                        let path = Path.Combine(folderName, a.Attribute(LocationAttributeName).Value)
                //                         let node =
                //                             AssemblyNode.GetAssembly(path, ResolverCache, true, true, true, true)
                //                        where node != null
                //                        select WithResolver(node)
                //                    ).ToArray();

                package.Resources =
                    (from r in package.RootFiles.Where(a => a.EndsWith(".resx"))
                     select new ResXResourceSet(Path.Combine(folderName, r))).ToArray();
            }


            return package;
        }

        private static void SetRootFiles(XDocument xDoc, Package package)
        {
            IEnumerable<XElement> manifestselements = xDoc.Root.Descendants(RootFileXName);
            package.RootFiles =
                manifestselements
                    .Select(a => a.Attribute(LocationAttributeName).Value)
                    .ToArray();
        }

       

        private static void SetFeatureManifests(XDocument xDoc, Package package)
        {
            var featureElementsElement = xDoc.Root.Element(FeaturesName);
            if (featureElementsElement != null)
            {

                IEnumerable<XElement> manifests = featureElementsElement.Elements(FeatureName);
                package.FeatureManifests = manifests
                    .Select(a => new Manifest {Location = a.Attribute(LocationAttributeName).Value})
                    .ToArray(); 
            }
        }

        private static void SetFeatures(string folderName, Package package)
        {
            package.Features = package.FeatureManifests.Select(a => ReadFeature(package, folderName, a)).ToArray();
        }

        //private static AssemblyNode WithResolver(AssemblyNode node)
        //{
        //    node.AssemblyReferenceResolutionAfterProbingFailed += node_AssemblyReferenceResolutionAfterProbingFailed;
        //    return node;
        //}


        
        private static SiteDefinition ReadSiteDefinition(XElement xElement)
        {
            var sd = new SiteDefinition
                         {
                             Name = xElement.Attribute("Location").Value,
                             WebTemps =
                                 xElement.Descendants(WebTempXName).Select(
                                     a => new Manifest {Location = a.Attribute("Location").Value}).ToArray()
                         };
            return sd;
        }

        private static Feature ReadFeature(Package package, string tempFolderName, Manifest manifest)
        {
            var fullFeatureXmlFilePath = Path.Combine(tempFolderName, manifest.Location);
            var featureXml = XDocument.Load(fullFeatureXmlFilePath);
            var root = featureXml.Root;

            Debug.Assert(root != null);

            var feature = new Feature(Path.GetDirectoryName(manifest.Location))
                              {
                                  Xml = root,
                                  Package = package,
                                  Scope = Helpers.ParseEnumValue<Scope>(root.Attribute("Scope").Value),
                                  Id = new Guid(root.Attribute("Id").Value),
                                  Title = root.Attribute("Title").Value,
                                  ReceiverTypeName = ReadFeatureReceiverTypeName(root),
                                  Dependencies = ReadFeatureDependencies(root),
                                  ElementManifests = ReadFeatureElementManifests(root)
                              };


            var allElements = new List<Element>(16);
            foreach (var elementManifest in feature.ElementManifests)
            {
                allElements.AddRange(ReadElements(package, feature, tempFolderName, elementManifest));
            }
            feature.Elements = allElements.ToArray();

            return feature;
        }

        private static string ReadFeatureReceiverTypeName(XElement root)
        {
            return root.Attribute("ReceiverClass") == null
                       ? string.Empty
                       : string.Format("{0}, {1}", root.Attribute("ReceiverClass").Value, root.Attribute("ReceiverAssembly").Value);
        }

        private static Manifest[] ReadFeatureElementManifests(XElement featureXml)
        {
            return featureXml
                .Descendants(ElementManifestName)
                .Select(a => new Manifest
                                 {
                                     Location = a.Attribute(LocationAttributeName).Value
                                 })
                .ToArray();
        }

        private static FeatureReference[] ReadFeatureDependencies(XElement featureXml)
        {
            return featureXml.Descendants(DependencyXName).Select(
                a =>
                new FeatureReference {Id = new Guid(a.Attribute("FeatureId").Value)}).
                ToArray();
        }

        private static IEnumerable<Element> ReadElements(Package package, Feature feature, string tempFolderName,
                                                         Manifest manifest)
        {
            string fullXmlFilePath = Path.Combine( Path.Combine(tempFolderName, feature.Folder), manifest.Location);
            XDocument elementsManifest = XDocument.Load(fullXmlFilePath);


            IEnumerable<Element> items = elementsManifest.Root.Elements().Select(
                element => new Element
                               {
                                   ElementType = Helpers.ParseEnumValue<ElementType>(element.Name.LocalName),
                                   Xml = element,
                                   Feature = feature,
                               });

            return items;
        }


        #region --- Events ---
        //static AssemblyNode node_AssemblyReferenceResolutionAfterProbingFailed(Microsoft.FxCop.Sdk.AssemblyReference assemblyReference, ModuleNode referencingModule)
        //{
        //    var path = Path.GetFullPath(".");
        //    var libs = Path.Combine(path, "lib");
        //    var files = Directory.GetFiles(libs, "*.dll", SearchOption.AllDirectories);
        //    if (files.Any(a => a.EndsWith(assemblyReference.Name + ".dll", StringComparison.InvariantCultureIgnoreCase)))
        //    {
        //        var item =
        //            files.FirstOrDefault(
        //                a => a.EndsWith(assemblyReference.Name + ".dll", StringComparison.InvariantCultureIgnoreCase));
        //        return AssemblyNode.GetAssembly(item);
        //    }
        //    return null;
        //} 
        #endregion
    
    }
}